﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AdvplDoc.Model;

namespace AdvplDoc.View {

    class HTMLPrinter : AbstractPrinter {

        public HTMLPrinter(string output_path) : base(output_path) {
            this.fileExtension = "html";
        }

		private string process_properties(List<AdvplProperty> properties) {
			StringBuilder sb  = new StringBuilder();
				
			sb.Append("<table border='1' width='100%'><tr bgcolor='#ccccff'>");
			sb.Append("<td colspan='2'><font size='+2'><b>Propriedades</b></font></td></tr>");

			foreach (AdvplProperty prop in properties) {
				sb.Append("<tr><td valign='top' align='right'>");
				sb.Append("<font face='monospace'>" + prop.type.ToString());
				sb.Append("</font></td>\n");
				sb.Append("<td valign='top' width='100%'>");
				sb.Append("<font face='monospace'><b>" + prop.name + "</b></font>");

				if (prop.hasMoreInfo()) {
					sb.Append("<table width='100%'><tr><td>&nbsp;&nbsp;");
					sb.Append("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>");
					sb.Append("<td width='100%'>");

					if (prop.isDeprecated) {
						sb.Append("<b>Obsoleto!</b> " +  prop.deprecatedInfo + "<br />\n");
					}

					if (prop.description != "") {
						sb.Append(prop.description + "<br />\n");
					}

					if (prop.since != "") {
						sb.Append(this.process_since(prop.since));
					}

					if (prop.seeAlso.Count > 0) {
						sb.Append(this.process_seealso(prop.seeAlso));
					}

					sb.Append("</td></tr></table>\n");
				}

				sb.Append("</td></tr>\n");
			}

			sb.Append("</table>\n");

			return sb.ToString();
		}

		private string process_method_summary(List<AdvplMethod> methods) {
			StringBuilder sb  = new StringBuilder();
			
			sb.Append("<table border='1' width='100%'><tr bgcolor='#ccccff'>");
			sb.Append("<td colspan='2'><font size='+2'>");
			sb.Append("<b>Resumo dos M&eacute;todos</b></font></td></tr>");

			int method_idx = 0;
			
			foreach (AdvplMethod method in methods) {
				sb.Append("<tr><td valign='top' align='right'>");
				sb.Append("<font face='monospace'>");
				
				if (method.returnInfo != null)
					sb.Append(method.returnInfo.type.ToString());

				sb.Append("</font></td>\n");
				sb.Append("<td valign='top' width='100%'>");
				sb.Append("<font face='monospace'><b>");
				sb.Append("<a href='#" + method.name.ToLower());
				sb.Append("()'>" + method.name + "</a></b>(");

				method_idx++;

				bool is_first_param = true;
				
				foreach (AdvplParam param in method.parameters) {
					if (is_first_param)
						is_first_param = false;
					else
						sb.Append(", ");

					sb.Append(param.type.ToString());

					if (param.name != "")
						sb.Append(" " + param.name);
				}

				sb.Append(")</font>");

				if (method.isDeprecated ||
				   (method.returnInfo != null && method.returnInfo.description != "")) {
					sb.Append("<table width='100%'><tr><td>&nbsp;&nbsp;");
					sb.Append("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>");
					sb.Append("<td width='100%'>");

					if (method.isDeprecated) {
						sb.Append("<b>Obsoleto!</b> ");
						sb.Append(method.deprecatedInfo + "<br />\n");
					}

					if (method.returnInfo != null && method.returnInfo.description != "")
						sb.Append(method.returnInfo.description + "<br />\n");

					sb.Append("</td></tr></table>\n");

				}

				sb.Append("</td></tr>\n");
			}

			sb.Append("</table>\n");

			return sb.ToString();
		}

		private string process_method_detail(List<AdvplMethod> methods) {
			StringBuilder mystr = new StringBuilder();
			mystr.Append("<table border='1' width='100%'><tr bgcolor='#ccccff'>");
                mystr.Append("<td><font size='+2'><b>Detalhes dos M&eacute;todos</b>");
                mystr.Append("</font></td></tr></table>");
			
			int method_idx = 0;

			foreach (AdvplMethod method in methods) {
				if (method_idx > 0)
					mystr.Append("<hr />\n");

				//local link
				mystr.Append("<a name='" + method.name.ToLower() + "()'><!-- --></a>");

				method_idx++;

				mystr.Append("<p><b>" + method.name);
				mystr.Append("</b></p>");
				mystr.Append("<dl><dt><font face='monospace'>");

				if (method.returnInfo != null)
					mystr.Append("" + method.returnInfo.type.ToString());

				mystr.Append("<b>" + method.name + "</b>(");

				bool first_iteration = true;

				foreach (AdvplParam param in method.parameters) {
					if (first_iteration)
						first_iteration = false;
					else
						mystr.Append(", ");

					mystr.Append(param.type.ToString());
					if (param.name != "")
						mystr.Append(" " + param.name);
				}

				mystr.Append(")</p>\n");
				mystr.Append("</font></dt>");
				mystr.Append("<dd>");

				if (method.isDeprecated)
					mystr.Append("<p><b>Obsoleto!</b> " + method.deprecatedInfo  + "</p>");

				mystr.Append("<p>" + method.description + "</p>\n");
				mystr.Append(this.process_obs(method.observation));
				mystr.Append(this.process_params(method.parameters));
				mystr.Append(this.process_return(method.returnInfo));
				mystr.Append(this.process_examples(method.examples));
				mystr.Append(this.process_since(method.since));
				mystr.Append(this.process_seealso(method.seeAlso));

				mystr.Append("</dd></dl>");
			}

			return mystr.ToString();
		}

	
		private string process_params(List<AdvplParam> parameters) {
			StringBuilder mystr = new StringBuilder();

			if (parameters.Count > 0) {
				mystr.Append("<dl><dt><b>Par&acirc;metros:</b></dt>\n");
				
				foreach (AdvplParam param in parameters) {
					mystr.Append("<dd><font face='monospace'>");
					mystr.Append(param.type.ToString());

					if (param.name != "")
						mystr.Append(" <b>" + param.name + "</b>");

					mystr.Append("</font>");

					if (param.isRequired)
						mystr.Append(" (obrigat&oacute;rio)");
					else
						mystr.Append(" (opcional)");

					mystr.Append(" - " + param.description + "</dd>\n");
				}

				mystr.Append("</dl>");
			}

			return mystr.ToString();
		}


		private string process_return(AdvplReturnInfo returnInfo) {
			StringBuilder mystr = new StringBuilder();

			if (returnInfo != null) {
				mystr.Append("<dl><dt><b>Retorno:</b></dt>\n");
				mystr.Append("<dd><font face='monospace'>");
				mystr.Append(returnInfo.type.ToString());

				if (returnInfo.name != "")
					mystr.Append(" " + returnInfo.name);

				mystr.Append("</font> - " + returnInfo.description + "</dd>\n");
				mystr.Append("</dl>");
			}

			return mystr.ToString();
		}


		private string process_since(string since) {
			StringBuilder mystr = new StringBuilder();

			if (since.Length == 0)
				return "";

			mystr.Append("<dl><dt><b>Abrang&ecirc;ncia:</b></dt>\n");
			mystr.Append("<dd>" + since + "</dd></dl>");

			return mystr.ToString();
		}

		public string process_class(AdvplClass advplclass) {
			StringBuilder mystr = new StringBuilder();

			mystr.Append(this.html_header(advplclass.name));
			mystr.Append(this.html_body_begin());
			mystr.Append("<h2>Classe " + advplclass.name + "</h2>\n");
			mystr.Append("<hr />\n");

			if (advplclass.isDeprecated) {
				mystr.Append("<p align='justify'><b>Obsoleta!</b> ");
				mystr.Append(advplclass.deprecatedInfo);
				mystr.Append("</p>\n");
			}

			mystr.Append("<p align='justify'>" + advplclass.description + "</p>\n");
			mystr.Append(this.process_obs(advplclass.observation));
			mystr.Append(this.process_examples(advplclass.examples));
			mystr.Append(this.process_since(advplclass.since));
			mystr.Append(this.process_seealso(advplclass.seeAlso));
			mystr.Append("<hr />\n");
			mystr.Append(this.process_properties(advplclass.properties));
			mystr.Append("<br />\n");
			mystr.Append(this.process_method_summary(advplclass.methods));
			mystr.Append("<br />\n");
			mystr.Append(this.process_method_detail(advplclass.methods));
			mystr.Append("<br />\n");
			mystr.Append(this.html_body_end());
			mystr.Append(this.html_footer());

			return mystr.ToString();
		}


		private string process_obs(List<string> obs_list) {
			StringBuilder mystr = new StringBuilder();

			if (obs_list.Count > 0) {
				mystr.Append("<ul>\n");

				foreach (string obs in obs_list) {
					mystr.Append("<div align='justify'><li>");

					bool is_in_pre = false;

					foreach (string line in obs.Split('\n')) {
						if (line.Trim() == Constants.PRE_BEGIN_TAG) {
							mystr.Append("<font face='monospace'>");
							is_in_pre = true;
						}
						else if (line.Trim() == Constants.PRE_END_TAG) {
							mystr.Append("</font><br />");
							is_in_pre = false;
						}
						else {
							if (is_in_pre == true)
								mystr.Append(line.Replace(" ", "&nbsp;") + "<br />\n");
							else
								mystr.Append(line + "<br />\n");
						}
					}

					mystr.Append("</li></div>\n");
				}

				mystr.Append("</ul>\n");
			}

			return mystr.ToString();
		}


		private string process_examples(List<string> examples) {
			StringBuilder mystr = new StringBuilder();
			
			if (examples.Count == 0)
				return "";

			mystr.Append("<dl><dt><b>Exemplos:</b></dt>\n");
			mystr.Append("<dd>");

			foreach (string example in examples) {
				bool is_in_pre = false;

				foreach (string line in example.Split('\n')) {
					if (line.Trim() == Constants.PRE_BEGIN_TAG) {
						mystr.Append("<font face='monospace'>");
						is_in_pre = true;
					}
					else if (line.Trim() == Constants.PRE_END_TAG) {
						mystr.Append("</font><br />");
						is_in_pre = false;
					}
					else {
						if (is_in_pre == true)
							mystr.Append(line.Replace(" ", "&nbsp;") + "<br />\n");
						else
							mystr.Append(line + "<br />\n");
					}
				}
			}

			mystr.Append("</font></dd></dl>");

			return mystr.ToString();
		}

		private string html_header(string title) {
			StringBuilder mystr = new StringBuilder();

			mystr.Append("<html>\n");
			mystr.Append("<head>\n");
			mystr.Append("<title>" + title + "</title>\n");
			mystr.Append("<meta http-equiv='content-type' content='text/html;charset=utf-8' />\n");
			mystr.Append("</head>\n");

			return mystr.ToString();
		}


		private string html_body_begin() {
			return "<body>\n";
		}


		private string html_body_end() {
			return "</body>\n";
		}
		
		private string html_footer() {
			return "</html>";
		}
		
		private string process_function_summary(AdvplFunction function) {
			StringBuilder mystr = new StringBuilder();

			mystr.Append("<table border='1' width='100%'><tr bgcolor='#ccccff'>");
			mystr.Append("<td colspan='2'><font size='+2'>");
			mystr.Append("<b>Resumo</b></font></td></tr>");
			mystr.Append("<tr><td valign='top' align='right'>");
			mystr.Append("<font face='monospace'>");

			if (function.returnInfo != null)
				mystr.Append(function.returnInfo.type.ToString());

			mystr.Append("</font></td>\n");
			mystr.Append("<td valign='top' widht='100%'>");
			mystr.Append("<font face='monospace'><b>");
			mystr.Append("<a href='#" + function.name.ToLower());
			mystr.Append("()'>" + function.name + "</a></b>(");

			bool is_first_param = true;

			foreach (AdvplParam param in function.parameters) {
				if (is_first_param)
					is_first_param = false;
				else
					mystr.Append(", ");

				mystr.Append(param.type.ToString());

				if (param.name != "")
					mystr.Append(" " + param.name);
			}

			mystr.Append(")</font>");

			if (function.isDeprecated) {
				mystr.Append("<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;");
				mystr.Append("&nbsp;&nbsp;&nbsp;&nbsp;<b>Obsoleta!</b> ");
				mystr.Append(function.deprecatedInfo);
			}

			if ((function.returnInfo != null) && (function.returnInfo.description != "")) {
				mystr.Append("<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;");
				mystr.Append("&nbsp;&nbsp;&nbsp;&nbsp;" + function.returnInfo.description);
			}

			mystr.Append("</td></tr>\n");
			mystr.Append("</table>\n");

			return mystr.ToString();
		}
		
		private string process_function_details(AdvplFunction function) {
			StringBuilder mystr = new StringBuilder();

			mystr.Append("<table border='1' width='100%'><tr bgcolor='#ccccff'>");
			mystr.Append("<td><font size='+2'><b>Detalhes</b>");
			mystr.Append("</font></td></tr></table>");

			// local link
			mystr.Append("<a name='" + function.name.ToLower() + "()'><!-- --></a>");
			mystr.Append("<p><b>" + function.name);
			mystr.Append("</b></p>");
			mystr.Append("<dl><dt><font face='monospace'>");

			if (function.returnInfo != null)
				mystr.Append(function.returnInfo.type.ToString() + " ");

			mystr.Append("<b>" + function.name + "</b>(");

			bool first_iteration = true;
			
			foreach (AdvplParam param in function.parameters) {
				if (first_iteration)
					first_iteration = false;
				else
					mystr.Append(", ");

				mystr.Append(param.type.ToString());

				if (param.name != "")
					mystr.Append(" " + param.name);
			}

			mystr.Append(")</p>\n");
			mystr.Append("</font></dt>");
			mystr.Append("<dd>");

			if (function.isDeprecated)
				mystr.Append("<p><b>Obsoleta!</b> " + function.deprecatedInfo + "</p>");

			mystr.Append("<p>" + function.description + "</p>\n");

			mystr.Append(this.process_obs(function.observation));
			mystr.Append(this.process_params(function.parameters));
			mystr.Append(this.process_return(function.returnInfo));
			mystr.Append(this.process_examples(function.examples));
			mystr.Append(this.process_since(function.since));
			mystr.Append(this.process_seealso(function.seeAlso));

			mystr.Append("</dd></dl>");

			return mystr.ToString();
		}
		
		public string process_function(AdvplFunction advplfunction) {
			StringBuilder mystr = new StringBuilder();

			mystr.Append(this.html_header(advplfunction.name));
			mystr.Append(this.html_body_begin());
			mystr.Append("<h2>Fun&ccedil;&atilde;o " + advplfunction.name + "</h2>\n");
			mystr.Append("<hr />\n");
			mystr.Append(this.process_function_summary(advplfunction));
			mystr.Append("<br />\n");
			mystr.Append(this.process_function_details(advplfunction));
			mystr.Append("<br />\n");
			mystr.Append(this.html_body_end());
			mystr.Append(this.html_footer());

			return mystr.ToString();
		}

		public string make_summary(List<AdvplFunction> functions, List<AdvplClass> classes, bool use_frames = true) {
			StringBuilder mystr = new StringBuilder();

			mystr.Append(this.html_header("Todas as classes e fun&ccedil;&otilde;es"));
			mystr.Append(this.html_body_begin());

			mystr.Append("<img border='0' src='res/totvs-logo.png' /><br />\n");
			mystr.Append("<b><i>A Linguagem de Programa&ccedil;&atilde;o ");
			mystr.Append("ADVPL</i></b><br /><br />\n");

			if (functions != null) {
				mystr.Append("<b>Todas as Fun&ccedil;&otilde;es</b><br />");

				foreach (AdvplFunction f in functions) {
					mystr.Append("<a href='" +  this.get_function_file_name(f.name) + "'");
		
					if (use_frames)
						mystr.Append(" target='targetFrame'");

					mystr.Append(">" + f.name + "</a><br />");
				}

				mystr.Append("<br />");
			}

			if (classes != null) {
				mystr.Append("<b>Todas as Classes</b><br />");

				foreach (AdvplClass c in classes) {
					mystr.Append("<a href='" + this.get_class_file_name(c.name) + "'");

					if (use_frames)
						mystr.Append(" target='targetFrame'");

					mystr.Append(">" + c.name + "</a><br />");
				}
			}

			mystr.Append(this.html_body_end());
			mystr.Append(this.html_footer());

			return mystr.ToString();
		}
		
		public string make_summary_no_frames(List<AdvplFunction> functions, List<AdvplClass> classes) {
			return this.make_summary(functions, classes, false);
		}

		public string make_frames(string summary_file, string summary_file_no_frames) {
			StringBuilder mystr = new StringBuilder();

			mystr.Append(this.html_header("A Linguagem de Programa&ccedil;&atilde;o ADVPL"));
			mystr.Append("<frameset cols='20%,80%' title=''>\n");
			mystr.Append("<frame src='" + summary_file + "' name='summaryFrame'");
			mystr.Append(" title='All classes and functions' scrolling='yes'>\n");
			mystr.Append("<frame src='' name='targetFrame' ");
			mystr.Append("title='Class and function descriptions' scrolling='yes'>\n");

			mystr.Append("<noframes>\n");
			mystr.Append("<h2>Frame Alert</h2>\n");
			mystr.Append("<p>This document is designed to be viewed using the frames ");
			mystr.Append("feature. If you see this message, you are using a ");
			mystr.Append("non-frame-capable web client.<br/>\n");
			mystr.Append("Link to <a href='" + summary_file_no_frames+ "'>Non-frame ");
			mystr.Append("version.</a>");
			mystr.Append("</noframes>\n");

			mystr.Append("</frameset>\n");
			mystr.Append(this.html_footer());

			return mystr.ToString();
		}
		
		private string process_seealso(List<string> seealso) {
			StringBuilder mystr = new StringBuilder();
		 
			if (seealso.Count == 0)
				return "";

			mystr.Append("<dl>");
			mystr.Append("<dt><b>Veja tamb&eacute;m:</b></dt>\n");
			
			foreach (string entry in seealso)
				mystr.Append("<dd>" + entry + "</dd>\n");

			mystr.Append("</dl>");

			return mystr.ToString();
		}
		
		public string render_links(string html) {
			StringBuilder mystr = new StringBuilder();
			int html_len = html.Length;
			int i = 0;

			while (i < html_len) {
				int space = html.IndexOf(' ', i);
				bool hasCloseBracket = (space != -1) && (html.IndexOf(']', i, (space - i)) != -1);

				if ((html[i] == '#') &&
					(html_len >= i + 1) &&
					(html[i + 1] == '[') &&
					hasCloseBracket) {
					i += 2;

					string render = "";

					while (html[i] != ']') {
						render = render + html[i];
						i++;
					}

					if (render.StartsWith("function=")) {
						string function_name = render.Split('=')[1];

						mystr.Append("<a target='targetFrame' href='");
						mystr.Append(this.get_function_file_name(function_name) + "'>");
						mystr.Append("<b><font face='monospace'>");
						mystr.Append(function_name + "</font></b></a>");
					}
					else if (render.StartsWith("class=")) {
						string class_name = render.Split('=')[1];

						mystr.Append("<a target='targetFrame' ");
						mystr.Append("href='" + this.get_class_file_name(class_name));
						mystr.Append("'><b><font face='monospace'>");
						mystr.Append(class_name + "</font></b></a>");
					}
					else if (render.StartsWith("method=")) {
						string class_name = render.Split('=')[1].Split(':')[0];
						string method_name = render.Split('=')[1].Split(':')[1];

						mystr.Append("<a target='targetFrame' ");
						mystr.Append("href='" + this.get_class_file_name(class_name));
						mystr.Append("#" + method_name.ToLower() + "()'>");
						mystr.Append("<b><font face='monospace'>");
						mystr.Append(class_name + "::" + method_name + "</font></b></a>");
					}
				}
				else {
					mystr.Append(html[i]);
				}

				i++;
			}

			return mystr.ToString();
		}

		public string get_function_file_name(string name) {
			return "f_" + name.ToLower() + ".html";
		}

		public string get_class_file_name(string name) {
			return "c_" + name.ToLower() + ".html";
		}

        override internal void process(Parser parser, System.Action<int> start, System.Action<int> update) {
            start(parser.classes.Count + parser.functions.Count);
            int count = 0;

            foreach (AdvplClass c in parser.classes.Values) {
                string html_class = this.process_class(c);
                html_class = this.render_links(html_class);
                string filename = this.get_class_file_name(c.name);

                this.write(filename, html_class);

                update(count++);
            }

            foreach (AdvplFunction f in parser.functions) {
                string html_function = this.process_function(f);
                html_function = this.render_links(html_function);
                string filename = this.get_function_file_name(f.name);

                this.write(filename, html_function);

                update(parser.functions.IndexOf(f));
            }

            string summary_no_frames = this.make_summary_no_frames(parser.functions, parser.getClassList());
            this.write("advpl_summary_no_frames.html", summary_no_frames);


            string summary = this.make_summary(parser.functions, parser.getClassList());
            this.write("advpl_summary.html", summary);


            string index = this.make_frames("advpl_summary.html", "advpl_summary_no_frames.html");
            this.write("index.html", index);
        }
    }
}
